#include <Wire.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
/*MQTT连接配置*/
/*-----------------------------------------------------*/
const char* ssid = "xinghe";
const char* password = "1125wdnb2030@";
//const char* ssid = "SCU_Makers";
//const char* password = "iloveSCU";
const char* mqttServer = "0e7519d568.iot-mqtts.cn-north-4.myhuaweicloud.com";
const int   mqttPort = 1883;
//以下3个参数可以由HMACSHA256算法生成，为硬件通过MQTT协议接入华为云IoT平台的鉴权依据
const char* clientId = "65edb674fb8177243a4d9ad3_ESP32_WROOM_0_0_2024031101";
const char* mqttUser = "65edb674fb8177243a4d9ad3_ESP32_WROOM";
const char* mqttPassword = "1468886de1a18366abf780d939588b83df13bea668185a149d49b37a90b88a6b";
WiFiClient espClient; //ESP32WiFi模型定义
PubSubClient client(espClient);

/*注册设备的ID和密钥
#define device_id "65edb674fb8177243a4d9ad3_ESP32_WROOM" 
#define secret "bfe882f949a428abb4629c64b0926041" 
 */
const char* topic_properties_report = "$oc/devices/65edb674fb8177243a4d9ad3_ESP32_WROOM/sys/properties/report";
const char* topic_command = "$oc/devices/65edb674fb8177243a4d9ad3_ESP32_WROOM/sys/commands/#";//设备接收命令
//接收到命令后上发的响应topic
char* topic_Commands_Response = "$oc/devices/65edb674fb8177243a4d9ad3_ESP32_WROOM/sys/commands/response/request_id=";
/*
 * 作用：  ESP32的WiFi初始化以及与MQTT服务器的连接
 * 参数：  无
 * 返回值：无
 */
/*本地控制定义智能农业*/
#include<ESP32Servo.h>
#include <DHT.h>//包含DHT头文件
#include <stdio.h>
#define DHTPIN1 15   //定义DHT11模块连接管脚也可以通过gpio去进行定义大棚内部空气温湿度
#define DHTTYPE1 DHT11   // 使用温度湿度模块的类型为DHT11
#define DHTPIN2  18  //定义DHT11模块连接管脚也可以通过gpio去进行定义大鹏外部空气温湿度
#define DHTTYPE2 DHT11   // 使用温度湿度模块的类型为DHT11
#define LED_FREQ 20000      // 设置PWM通道号
#define LED_RESOLUTION 10   // 设置PWM分辨率（位数）
DHT dht1(DHTPIN1, DHTTYPE1);    //生成DHT对象，参数是引脚和DHT的类型
DHT dht2(DHTPIN2, DHTTYPE2);    //生成DHT对象，参数是引脚和DHT的类型
Servo shade_servo;  //遮荫舵机对象
int hwshui=0;//高灌溉舵机的水泵力量
int lwshui=0;//底灌溉的水泵力量
int LED_RB=0;//大棚补光的RGB参数
int LED_WG=0;
int sha_PWM=0;//遮荫的强度大小
int fans_PWM=0;//风机的强度大小

unsigned char item[8] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x07, 0x04, 0x08}; //土壤传感器命令
float humi_in = 0, temp_in = 0,humi_air = 0, temp_air = 0,humi_grd=0,temp_grd=0;//定义浮点型全局变量 储存传感器读取的温湿度数据
int light=0;//大鹏内部光照强度
int u_strength=0;//室外外紫外线强PH度
float PH=7;//土壤的PH值
int ec=0;//土壤导电率
int Nin=0;//土壤氮含量
int Pin=0;//土壤磷含量
int Kin=0;//土壤钾含量
int rain_state=0;//用于描述外部环境下雨的大小，0-100。
int CO2=0;//大棚内部的CO2的浓度
int Control=0;//用于判断控制什么电控
long past=0;//用于判断属性上报的时间
long Past=0;//用于采样的时间控制
long Past_DHT=0;//用于控制DHT的采样间隔时间
bool Past_test=0;//用于表示DHT是否采样

/*
 * 作用：  ESP32的WiFi初始化以及与MQTT服务器的连接
 * 参数：  无
 * 返回值：无
 */
void MQTT_Init()
{
    //WiFi网络连接部分
  WiFi.begin(ssid, password); //开启ESP32的WiFi
  while (WiFi.status() != WL_CONNECTED) { //ESP尝试连接到WiFi网络
    delay(3000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to the WiFi network");
 
  //MQTT服务器连接部分
  client.setServer(mqttServer, mqttPort); //设置连接到MQTT服务器的参数
 
  client.setKeepAlive (60); //设置心跳时间
 
  while (!client.connected()) { //尝试与MQTT服务器建立连接
    Serial.println("Connecting to MQTT...");
  
    if (client.connect(clientId, mqttUser, mqttPassword )) {
  
      Serial.println("connected");  
  
    } else {
  
      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(6000);
  
    }
  }
  //接受平台下发内容的初始化
  client.setCallback(callback); //可以接受任何平台下发的内容
}
/*板子传感器信息属性上报数据的定义*/
 void MReport_Sensor_inform1()
{
  String JSONmessageBuffer;//定义字符串接收序列化好的JSON数据
  //以下将生成好的JSON格式消息格式化输出到字符数组中，便于下面通过PubSubClient库发送到服务器
  StaticJsonDocument<256> doc;

  JsonObject services_0 = doc["services"].createNestedObject();
  services_0["service_id"] = "sensor";

  JsonObject services_0_properties = services_0.createNestedObject("properties");
  services_0_properties["temp_ground"] = temp_grd;
  services_0_properties["temp_air"] = temp_air;
  services_0_properties["temp_iair"] = temp_in;
  services_0_properties["humi_ground"] = humi_grd;
  services_0_properties["humi_air"] = humi_air;


  serializeJson(doc, JSONmessageBuffer);

    Serial.println("Sending message to MQTT topic..");
    Serial.println(JSONmessageBuffer);
    
    if(client.publish(topic_properties_report,JSONmessageBuffer.c_str())==true)//使用c_str函数将string转换为char
    {
      Serial.println("Success sending message");
    }else{
      Serial.println("Error sending message");
    }
}
 void MReport_Sensor_inform2()
{
  String JSONmessageBuffer;//定义字符串接收序列化好的JSON数据
  //以下将生成好的JSON格式消息格式化输出到字符数组中，便于下面通过PubSubClient库发送到服务器
  StaticJsonDocument<256> doc;

  JsonObject services_0 = doc["services"].createNestedObject();
  services_0["service_id"] = "sensor";

  JsonObject services_0_properties = services_0.createNestedObject("properties");
  services_0_properties["humi_iair"] = humi_in;
  services_0_properties["light_strength"] = light;
  services_0_properties["u_strength"] = u_strength;
  services_0_properties["PH"] = PH;
  services_0_properties["ec"] = ec;


  serializeJson(doc, JSONmessageBuffer);

    Serial.println("Sending message to MQTT topic..");
    Serial.println(JSONmessageBuffer);
    
    if(client.publish(topic_properties_report,JSONmessageBuffer.c_str())==true)//使用c_str函数将string转换为char
    {
      Serial.println("Success sending message");
    }else{
      Serial.println("Error sending message");
    }
}
 void MReport_Sensor_inform3()
{
  String JSONmessageBuffer;//定义字符串接收序列化好的JSON数据
  //以下将生成好的JSON格式消息格式化输出到字符数组中，便于下面通过PubSubClient库发送到服务器
  StaticJsonDocument<256> doc;

  JsonObject services_0 = doc["services"].createNestedObject();
  services_0["service_id"] = "sensor";

  JsonObject services_0_properties = services_0.createNestedObject("properties");
  services_0_properties["co2"] = CO2;
  services_0_properties["rain_state"] = rain_state;
  services_0_properties["Nin"] = Nin;
  services_0_properties["Kin"] = Kin;
  services_0_properties["Pin"] = Pin;

  serializeJson(doc, JSONmessageBuffer);

    Serial.println("Sending message to MQTT topic..");
    Serial.println(JSONmessageBuffer);
    
    if(client.publish(topic_properties_report,JSONmessageBuffer.c_str())==true)//使用c_str函数将string转换为char
    {
      Serial.println("Success sending message");
    }else{
      Serial.println("Error sending message");
    }
}
//解析华为云平台下发出来的数据，甄别是那个指令，并为相关的数据赋值
void callback(char *topic,byte *payload,unsigned int length)
{

  char *pstr = topic; //指向topic字符串，提取request_id用
 
  /*串口打印出收到的平台消息或者命令*/
  Serial.println();
  Serial.println();
  Serial.print("Message arrived [");
  Serial.print(topic);  //将收到消息的topic展示出来
  Serial.print("] ");
  Serial.println();
 
  payload[length] = '\0'; //在收到的内容后面加上字符串结束符
  char strPayload[255] = {0}; 
  strcpy(strPayload, (const char*)payload);
  Serial.println((char *)payload);  //打印出收到的内容
  Serial.println(strPayload);
 
 
  /*request_id解析部分*///后文有详细解释为什么要提取下发命令的request_id
  char arr[100];  //存放request_id
  int flag = 0;
  char *p = arr;
  while(*pstr)  //以'='为标志，提取出request_id
  {
    if(flag) *p ++ = *pstr;
    if(*pstr == '=') flag = 1;
    pstr++;
  }
  *p = '\0';  
  Serial.println(arr);
 
 
  /*将命令响应topic与resquest_id结合起来*/
  char topicRes[200] = {0};
  strcat(topicRes, topic_Commands_Response);
  strcat(topicRes, arr);
  Serial.println(topicRes);

 // Stream& input;

  StaticJsonDocument<256> doc;

  DeserializationError error = deserializeJson(doc, payload);

  if (error) {
    Serial.print("deserializeJson() failed: ");
    Serial.println(error.c_str());
    return;
  }


  const char* service_id = doc["service_id"]; // "machine_control"
  const char* command_name = doc["command_name"]; 
// "door_open-1、car_door_open-2、LED_control-3、window_control-4、fire_alarm_off-5、gas_leak_alarm_off-6、home_model-7" 
//
  switch(command_name[0])
  {
    case 'h':
      Control=1;
      hwshui=doc["paras"]["water_power"];
      break;
    case 'l':
      Control=2;
      lwshui=doc["paras"]["water_power"];
      break;
    case 's':
      Control=3;
      sha_PWM=doc["paras"]["sha_power"];
      break;
    case 'R':
      Control=4;
      LED_RB=doc["paras"]["Red_Blue"];
      LED_WG=doc["paras"]["White_Green"];
      break;
    case 'f':
      Control=5;
      fans_PWM=doc["paras"]["fans_PWM"];
      break;
  }
  machine_control(Control);//根据参数执行对应的动作
  Control=-1;
  MQTT_response(topicRes);//发送响应参数
}
//有响应参数的命令的命令响应参数上报
void MQTT_response(char *topic)
{
 String responsed;
 StaticJsonDocument<128> doc;
 JsonObject response = doc.createNestedObject("response");
 serializeJson(doc, responsed);
 client.publish(topic,responsed.c_str());
 Serial.println(responsed);
}
//本地传感器和控制舵机初始化
void Detect_Control_Init()
{
  ledcSetup(0,LED_FREQ, LED_RESOLUTION);
  ledcSetup(1,LED_FREQ, LED_RESOLUTION);
  ledcSetup(2,LED_FREQ, LED_RESOLUTION);
  ledcSetup(3,LED_FREQ, LED_RESOLUTION);
  ledcSetup(4,LED_FREQ, LED_RESOLUTION);
  ledcAttachPin(25, 4);  //定义多个PWM信号控制多个RGB,RB
  ledcAttachPin(26, 3);//WG
  ledcAttachPin(27, 2);//l
  ledcAttachPin(14, 1);//h
  ledcAttachPin(12, 0);//定义PWM信号控制风机大小
  ledcWrite(0,0);
  ledcWrite(1,0);
  ledcWrite(2,0);
  ledcWrite(3,0);
  ledcWrite(4,0);
}
//执行华为云的命令,并上报更新设备属性
void machine_control(int Control)
{
  if(Control==1)
  {
    hw_control();
  }
  else if(Control==2)
  {
    lw_control();
  }
  else if(Control==3)
  {
    shade_control();
  }
  else if(Control==4)
  {
    LED_control();
  }
  else if(Control==5)
  {
    fans_control();
  }
}
//传感器信息定时上报
void Periodic_report()
{
  long now=millis();
  if (now - Past > 9000)//定时上报
  {
    Past = now;
    Detect();
    DHT_1();
  }

  if (now - past > 25000)//定时上报
  {
    past = now;
    MReport_Sensor_inform1();
    delay(50);
    MReport_Sensor_inform2();
    delay(50);
    MReport_Sensor_inform3();
    client.setCallback(callback);
  }
}
//传感器采样的程序
void DHT_1()
{
  //大棚内部温湿度
    float h = dht1.readHumidity();//读取DHT11传感器的湿度 并赋值给h
    float t = dht1.readTemperature();//读取传感器的温度   并赋值给t
    if (isnan(h) || isnan(t))//判断是否成功读取到温湿度数据
    {
        Serial.println("Failed to read from DHT1 sensor!");//读取温湿度失败！
    }
    else//成功读取到数据
    {
        humi_in = h;//将读取到的湿度赋值给全局变量humi_read
        temp_in = t;//将读取到的温度赋值给全局变量temp_read
        Serial.print("hi");Serial.print(humi_in);Serial.print("ti");Serial.println(temp_in);
    }
}

/*void DHT_2()
{
  //室外空气温湿度
    float H = dht2.readHumidity();//读取DHT11传感器的湿度 并赋值给h
    float T = dht2.readTemperature();//读取传感器的温度   并赋值给t
    if (isnan(H) || isnan(T))//判断是否成功读取到温湿度数据
    {
        Serial.println("Failed to read from DHT2 sensor!");//读取温湿度失败！
    }
    else//成功读取到数据
    {
        humi_air = H;//将读取到的湿度赋值给全局变量humi_read
        temp_air = T;//将读取到的温度赋值给全局变量temp_read
        Serial.print("ho");Serial.print(humi_air);Serial.print("to");Serial.println(temp_air);
    }
}
*/
void Detect()
{
    light=analogRead(35);
    u_strength=analogRead(34);
    rain_state=analogRead(32);
    CO2=analogRead(33);
    //土壤传感器的串口通讯
  String data = ""; 
  String info[19];
   for (int i = 0 ; i < 8; i++) {  // 发送测量命令，包括含水率、温度、导电率、PH值、氮含量、磷含量、钾含量
     Serial2.write(item[i]);   // write输出
   }
   delay(100);  // 等待测温数据返回
    data = "";
   while (Serial2.available()) {//从串口中读取数据
      unsigned char in = (unsigned char)Serial2.read();  // read读取
      data += in;
      data += ',';
    }
    if (data.length() > 0) { //先输出一下接收到的数据
      int commaPosition = -1;
      // 用字符串数组存储
      for (int i = 0; i < 19; i++) {
      commaPosition = data.indexOf(',');
      if (commaPosition != -1)
      {
        info[i] = data.substring(0, commaPosition);
        data = data.substring(commaPosition + 1, data.length());
      }
      else {
        if (data.length() > 0) {  
          info[i] = data.substring(0, commaPosition);
        }
      }
    }
    }
  humi_grd =(info[3].toInt() * 256 + info[4].toInt())/10.0;
  temp_grd = (info[5].toInt() * 256 + info[6].toInt()<32768)?(info[5].toInt() * 256 + info[6].toInt())/10.0:(info[5].toInt() * 256 + info[6].toInt()-65536)/10.0;
  ec =(info[7].toInt() * 256 + info[8].toInt());
  PH =(info[9].toInt() * 256 + info[10].toInt())/10.0;
  Nin =(info[11].toInt() * 256 + info[12].toInt());
  Pin =(info[13].toInt() * 256 + info[14].toInt());
  Kin =(info[13].toInt() * 256 + info[14].toInt());
  Serial.print("土壤湿度:");  
  Serial.print(humi_grd);  
  Serial.print("%\n"); // 假设湿度是以百分比表示的  
    
  Serial.print("土壤温度:");  
  Serial.print(temp_grd);  
  Serial.print("℃\n"); // 温度以摄氏度为单位  
    
  Serial.print("土壤导电率:");  
  Serial.print(ec);  
  Serial.print("μS/cm\n"); // 导电率单位  
    
  Serial.print("土壤PH值:");  
  Serial.print(PH);  
  Serial.println(); // PH值后面直接换行，没有单位  
    
  Serial.print("土壤氮含量:");  
  Serial.print(Nin);  
  Serial.print("mg/kg\n"); // 氮含量单位  
    
  Serial.print("土壤磷含量:");  
  Serial.print(Pin);  
  Serial.print("mg/kg\n"); // 磷含量单位  
    
  Serial.print("土壤钾含量:");  
  Serial.print(Kin);  
  Serial.println("mg/kg"); // 钾含量单位，并在最后换行
}

void shade_control()
{
  shade_servo.write(sha_PWM);
}

void hw_control()
{
  ledcWrite(1,hwshui);
}

void lw_control()
{
  ledcWrite(2,lwshui);
}

void LED_control()
{
  ledcWrite(4, LED_RB);
  ledcWrite(3, LED_WG);
}

void fans_control()
{
  ledcWrite(0,fans_PWM);
}


void setup()
{
  Serial.begin(112500);
  Serial2.begin(4800);
  MQTT_Init();
  Detect_Control_Init();
  shade_servo.attach(22);  //遮荫舵机对象
  shade_servo.write(90);
}
void loop()
{  
  Periodic_report();
  client.loop();//保持硬件活跃度
}
 
/*******************************************************/